/* 
Copyright (c) 2008 Delio Brignoli

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

*/

#include <avr/interrupt.h>

#include <Debug.h>
dbg_filename_init();

#include <Assert.h>
assert_filename_init();

#define Adac_c
#include "Adac.h"
#include "WProgram.h"

/* Private API functions */

static inline void _adac_enable_dac(void) {
  ADCSRA |= _BV(ADEN) | _BV(ADIE);
}

static inline void _adac_disable_dac(void) {
  ADCSRA &= ~(_BV(ADEN) | _BV(ADIE));
}

static inline void _adac_trigger_conversion(void) {
  ADCSRA |= _BV(ADSC);
}

static inline void _adac_abort_conversion(void) {
  ADCSRA &= ~(_BV(ADSC));
}

static inline void _adac_select_input( int input_index ) {
  ADMUX = (ADMUX & ~(_BV(MUX0)|_BV(MUX1)|_BV(MUX2)|_BV(MUX3))) | ( input_index & (_BV(MUX0)|_BV(MUX1)|_BV(MUX2)|_BV(MUX3)) );
}

static inline void _adac_select_voltage_reference( adac_voltage_ref_t reference ) {
  ADMUX = (ADMUX & ~(_BV(REFS0)|_BV(REFS1))) | reference;
}

static inline void _adac_select_result_alignment( adac_result_alignment_t alignment ) {
  ADMUX = (ADMUX & ~_BV(ADLAR)) | alignment;
}

static inline void _adac_select_trigger_mode( adac_trigger_mode_t mode ) {
  ADCSRA |= mode;
}

static inline adac_trigger_mode_t _adac_trigger_mode(void) {
  return ADCSRA & _BV(ADATE);
}

static inline void _adac_select_trigger_source( adac_trigger_source_t source ) {
  ADCSRB = ( ADCSRB & ~(_BV(ADTS0)|_BV(ADTS1)|_BV(ADTS2))) | source;
}

static inline void _adac_clear_interrupt_flag(void) {
  ADCSRA |= _BV(ADIF);
}

static inline void _adac_set_dac_clock_div( adac_clock_div_t clock_div ) {
  ADCSRA = ( ADCSRA & ~(_BV(ADPS0)|_BV(ADPS1)|_BV(ADPS2))) | clock_div;
}

static inline void _adac_disable_digital_buffer_on_input( int input_index ) {
  DIDR0 |= _BV(input_index) & (_BV(ADC5D)|_BV(ADC4D)|_BV(ADC3D)|_BV(ADC2D)|_BV(ADC1D)|_BV(ADC0D));
}

static inline int _adac_prepare_next_conversion(void) {
  if (_adac_priv.next_input < 0)
    return _adac_priv.next_input;

  _adac_select_input( _adac_priv.next_input );
  _adac_set_dac_clock_div( _adac_priv.next_clock_div );
  _adac_select_voltage_reference( _adac_priv.next_voltage_reference );
  _adac_select_result_alignment( _adac_priv.next_result_alignment );
  _adac_select_trigger_source( _adac_priv.next_trigger_source );
  _adac_select_trigger_mode( _adac_priv.next_trigger_mode );
  return ADAC_ERROR_OK;
}

static inline void _adac_pause( void ) {
  _adac_select_trigger_mode( ADAC_TRIGGER_MODE_EXPLICIT );
  _adac_abort_conversion();
  _adac_select_trigger_source( ADAC_TRIGGER_SOURCE_DONTCARE );
}

static inline void _adac_stop( void ) {
  int idx;
  _adac_abort_conversion();
  _adac_disable_dac();

  //Re-enable digital buffers on inputs
  for (idx = 0; idx < _adac_priv.inputs_num; idx++) {
    _adac_disable_digital_buffer_on_input( _adac_priv.inputs_array[idx] );
  }
}

void _dummy_completition_callback(int cc, void* priv) {
  _adac_priv.next_input = -1;
}

//Standard IRQ handler
ISR(ADC_vect, ISR_NOBLOCK) {
  _adac_priv.conversion_complete_callback( ++_adac_priv.conversion_num, _adac_priv.callback_priv );
  //Should sampling stop?
  if (_adac_prepare_next_conversion() >= 0) {
    _adac_clear_interrupt_flag();
  }
}

/* Public API functions */

inline adac_state_t adac_get_state(void) {
  if (!bit_is_set( ADCSRA, ADEN )) {
    assert(!bit_is_set( ADCSRA, ADIE ));
    assert(_adac_priv.next_input < 0);
    return ADAC_STATE_STOPPED;
  }

  boolean in_progress = bit_is_set( ADCSRA, ADSC );
  if (in_progress) {
    assert(_adac_priv.next_input >= 0);
    return ADAC_STATE_RUNNING;
  } else {
	  if (_adac_priv.next_input >= 0 && _adac_trigger_mode() == ADAC_TRIGGER_MODE_EXPLICIT) {
		return ADAC_STATE_READY;
	  }

	  if (_adac_priv.next_input >= 0 && _adac_trigger_mode() != ADAC_TRIGGER_MODE_EXPLICIT) {
		return ADAC_STATE_RUNNING;
	  }
		
	  if (_adac_priv.next_input < 0) {
	    assert(_adac_priv.conversion_num > 0);
	    assert(_adac_trigger_mode() == ADAC_TRIGGER_MODE_EXPLICIT);
		return ADAC_STATE_FINISHED;
	  }
  }
  
  return ADAC_STATE_INVALID;
}

int adac_init(void) {
  _adac_priv.conversion_num = 0;
  _adac_priv.inputs_array = NULL;
  _adac_priv.inputs_num = 0;
  _adac_priv.next_input = -99;
  _adac_priv.callback_priv = NULL;
  _adac_priv.conversion_complete_callback = _dummy_completition_callback;

  _adac_disable_dac();

  return ADAC_ERROR_OK;
}

int adac_prepare( int *inputs_array, size_t inputs_num, 
                                 void (*conversion_complete_callback)(int, void*),
							     void *priv ) {
  int idx;

  adac_state_t state = adac_get_state();
  if ( state == ADAC_STATE_RUNNING )
    return ADAC_ERROR_INVALID_STATE;

  if (conversion_complete_callback == NULL)
    return ADAC_ERROR_INVALID_VALUE;

  _adac_priv.conversion_num = 0;
  _adac_priv.inputs_array = inputs_array;
  _adac_priv.inputs_num = inputs_num;

  //Disable digital buffers on inputs
  for (idx = 0; idx < _adac_priv.inputs_num; idx++) {
    _adac_disable_digital_buffer_on_input( _adac_priv.inputs_array[idx] );
  }

  _adac_priv.callback_priv = priv;
  _adac_priv.conversion_complete_callback = conversion_complete_callback;

  _adac_priv.conversion_complete_callback( _adac_priv.conversion_num, priv );
  _adac_pause();
  //_adac_prepare_next_conversion();

  _adac_enable_dac();

  return ADAC_ERROR_OK;
}

int adac_trigger( void ) {
  adac_state_t state = adac_get_state();
  if ( state != ADAC_STATE_READY ) {
    dbg_printf_1(error, "adac_get_state()=%d should be ADAC_STATE_READY", state);
    return ADAC_ERROR_INVALID_STATE;
  }

  assert( _adac_prepare_next_conversion() == ADAC_ERROR_OK );
  _adac_trigger_conversion();

  return ADAC_ERROR_OK;
}

int adac_pause( void ) {
  adac_state_t state = adac_get_state();
  if ( state == ADAC_STATE_READY )
    return ADAC_ERROR_OK;
  if ( state == ADAC_STATE_STOPPED || state == ADAC_STATE_FINISHED )
    return ADAC_ERROR_INVALID_STATE;

  _adac_pause();
  return ADAC_ERROR_OK;
}

int adac_stop( void ) {
  adac_state_t state = adac_get_state();
  if ( state == ADAC_STATE_STOPPED )
    return ADAC_ERROR_OK;
  if ( state == ADAC_STATE_INVALID || state == ADAC_STATE_RUNNING )
    return ADAC_ERROR_INVALID_STATE;

  _adac_stop();
  return ADAC_ERROR_OK;
}
